// Class filesystem::directory_entry etc. -*- C++ -*-
// Copyright (C) 2014-2017 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.
#ifndef _GLIBCXX_USE_CXX11_ABI
#    define _GLIBCXX_USE_CXX11_ABI 1
#endif
#include <filesystem>
#include <utility>
#include <stack>
#include <string.h>
#include <errno.h>
#define _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM namespace filesystem {
#define _GLIBCXX_END_NAMESPACE_FILESYSTEM }
#include "dir-common.h"
namespace fs = std::filesystem;
struct fs::_Dir : _Dir_base {
    _Dir(const fs::path& p, bool skip_permission_denied, error_code& ec) : _Dir_base(p.c_str(), skip_permission_denied, ec) {
        if (!ec)
            path = p;
    }
    _Dir(DIR* dirp, const path& p) : _Dir_base(dirp), path(p) {}
    _Dir(_Dir&&) = default;
    // Returns false when the end of the directory entries is reached.
    // Reports errors by setting ec.
    bool advance(bool skip_permission_denied, error_code& ec) noexcept {
        if (const auto entp = _Dir_base::advance(skip_permission_denied, ec)) {
            entry = fs::directory_entry { path / entp->d_name, get_file_type(*entp) };
            return true;
        } else if (!ec) {
            // reached the end
            entry = {};
        }
        return false;
    }
    bool advance(error_code& ec) noexcept { return advance(false, ec); }
    // Returns false when the end of the directory entries is reached.
    // Reports errors by throwing.
    bool advance(bool skip_permission_denied = false) {
        error_code ec;
        const bool ok = advance(skip_permission_denied, ec);
        if (ec)
            _GLIBCXX_THROW_OR_ABORT(filesystem_error("directory iterator cannot advance", ec));
        return ok;
    }
    bool should_recurse(bool follow_symlink, error_code& ec) const {
        file_type type = entry._M_type;
        if (type == file_type::none || type == file_type::unknown) {
            type = entry.symlink_status(ec).type();
            if (ec)
                return false;
        }
        if (type == file_type::directory)
            return true;
        if (type == file_type::symlink)
            return follow_symlink && is_directory(entry.status(ec));
        return false;
    }
    fs::path        path;
    directory_entry entry;
};
namespace {
    template <typename Bitmask>
    inline bool is_set(Bitmask obj, Bitmask bits) {
        return (obj & bits) != Bitmask::none;
    }
}
fs::directory_iterator::directory_iterator(const path& p, directory_options options, error_code* ecptr) {
    const bool skip_permission_denied = is_set(options, directory_options::skip_permission_denied);
    error_code ec;
    _Dir       dir(p, skip_permission_denied, ec);
    if (dir.dirp) {
        auto sp = std::make_shared<fs::_Dir>(std::move(dir));
        if (sp->advance(skip_permission_denied, ec))
            _M_dir.swap(sp);
    }
    if (ecptr)
        *ecptr = ec;
    else if (ec)
        _GLIBCXX_THROW_OR_ABORT(fs::filesystem_error("directory iterator cannot open directory", p, ec));
}
const fs::directory_entry& fs::directory_iterator::operator*() const {
    if (!_M_dir)
        _GLIBCXX_THROW_OR_ABORT(filesystem_error("non-dereferenceable directory iterator", std::make_error_code(errc::invalid_argument)));
    return _M_dir->entry;
}
fs::directory_iterator& fs::directory_iterator::operator++() {
    if (!_M_dir)
        _GLIBCXX_THROW_OR_ABORT(
            filesystem_error("cannot advance non-dereferenceable directory iterator", std::make_error_code(errc::invalid_argument)));
    if (!_M_dir->advance())
        _M_dir.reset();
    return *this;
}
fs::directory_iterator& fs::directory_iterator::increment(error_code& ec) {
    if (!_M_dir) {
        ec = std::make_error_code(errc::invalid_argument);
        return *this;
    }
    if (!_M_dir->advance(ec))
        _M_dir.reset();
    return *this;
}
struct fs::recursive_directory_iterator::_Dir_stack : std::stack<_Dir> {
    void clear() { c.clear(); }
};
fs::recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, error_code* ecptr) :
    _M_options(options), _M_pending(true) {
    if (DIR* dirp = ::opendir(p.c_str())) {
        if (ecptr)
            ecptr->clear();
        auto sp = std::make_shared<_Dir_stack>();
        sp->push(_Dir { dirp, p });
        if (ecptr ? sp->top().advance(*ecptr) : sp->top().advance())
            _M_dirs.swap(sp);
    } else {
        const int err = errno;
        if (err == EACCES && is_set(options, fs::directory_options::skip_permission_denied)) {
            if (ecptr)
                ecptr->clear();
            return;
        }
        if (!ecptr)
            _GLIBCXX_THROW_OR_ABORT(
                filesystem_error("recursive directory iterator cannot open directory", p, std::error_code(err, std::generic_category())));
        ecptr->assign(err, std::generic_category());
    }
}
fs::recursive_directory_iterator::~recursive_directory_iterator() = default;
int fs::recursive_directory_iterator::depth() const {
    return int(_M_dirs->size()) - 1;
}
const fs::directory_entry& fs::recursive_directory_iterator::operator*() const {
    return _M_dirs->top().entry;
}
fs::recursive_directory_iterator& fs::recursive_directory_iterator::operator=(const recursive_directory_iterator& other) noexcept = default;
fs::recursive_directory_iterator& fs::recursive_directory_iterator::operator=(recursive_directory_iterator&& other) noexcept = default;
fs::recursive_directory_iterator& fs::recursive_directory_iterator::operator++() {
    error_code ec;
    increment(ec);
    if (ec)
        _GLIBCXX_THROW_OR_ABORT(filesystem_error("cannot increment recursive directory iterator", ec));
    return *this;
}
fs::recursive_directory_iterator& fs::recursive_directory_iterator::increment(error_code& ec) {
    if (!_M_dirs) {
        ec = std::make_error_code(errc::invalid_argument);
        return *this;
    }
    const bool follow                 = is_set(_M_options, directory_options::follow_directory_symlink);
    const bool skip_permission_denied = is_set(_M_options, directory_options::skip_permission_denied);
    auto&      top                    = _M_dirs->top();
    if (std::exchange(_M_pending, true) && top.should_recurse(follow, ec)) {
        _Dir dir(top.entry.path(), skip_permission_denied, ec);
        if (ec) {
            _M_dirs.reset();
            return *this;
        }
        if (dir.dirp)
            _M_dirs->push(std::move(dir));
    }
    while (!_M_dirs->top().advance(skip_permission_denied, ec) && !ec) {
        _M_dirs->pop();
        if (_M_dirs->empty()) {
            _M_dirs.reset();
            return *this;
        }
    }
    return *this;
}
void fs::recursive_directory_iterator::pop(error_code& ec) {
    if (!_M_dirs) {
        ec = std::make_error_code(errc::invalid_argument);
        return;
    }
    const bool skip_permission_denied = is_set(_M_options, directory_options::skip_permission_denied);
    do {
        _M_dirs->pop();
        if (_M_dirs->empty()) {
            _M_dirs.reset();
            ec.clear();
            return;
        }
    } while (!_M_dirs->top().advance(skip_permission_denied, ec));
}
void fs::recursive_directory_iterator::pop() {
    error_code ec;
    pop(ec);
    if (ec)
        _GLIBCXX_THROW_OR_ABORT(filesystem_error(
            _M_dirs ? "recursive directory iterator cannot pop" : "non-dereferenceable recursive directory iterator cannot pop", ec));
}
